"""Checkout active CKI projects to a local directory."""
import argparse
import configparser
import dataclasses
import os
import pathlib
import re
import subprocess
import sys
import time
import typing

from cki_lib import misc
from cki_lib.gitlab import get_instance
import git
import sentry_sdk


@dataclasses.dataclass
class Group:
    """GitLab group config."""

    instance_url: str
    path: str
    blocklist: typing.List[str] = dataclasses.field(default_factory=list)
    allowlist: typing.List[str] = dataclasses.field(default_factory=list)
    aliases: typing.Dict[str, str] = dataclasses.field(default_factory=dict)


groups = [
    Group('https://gitlab.com', 'cki-project', blocklist=[
        'experimental/',
        'infrastructure',
        'kernel-ark',
    ]),
    Group('https://gitlab.com', 'redhat/centos-stream/tests/kernel', allowlist=[
        'kernel-tests',
        'kpet-db',
    ]),
    Group('https://gitlab.cee.redhat.com', 'cki-project', aliases={
        'documentation': 'internal-documentation',
    }),
]


class RepoManager:
    """Manage a CKI repository setup."""

    def __init__(self, gl_project, project_name, directory, force):
        """Initialize the instance."""
        self.gl_instance = gl_project.manager.gitlab
        self.gl_project = self.gl_instance.projects.get(gl_project.id)
        self.force = force

        project_path = re.sub('[_/]', '-', project_name)
        self.full_project_path = os.path.join(directory, project_path)

    def clone(self):
        """Clone the repo if the repo directory does not exist."""
        if os.path.exists(self.full_project_path):
            return
        print(f'Cloning {self.gl_project.path_with_namespace} to {self.full_project_path}')
        if not self.force:
            print('  skipping without --force')
            return
        git.Repo.clone_from(self.gl_project.ssh_url_to_repo, self.full_project_path)

    def create_fork(self):
        """Set up fork."""
        if not os.path.exists(self.full_project_path):
            return
        if self.gl_project.forks.list(owned=True):
            return
        print(f'Creating fork for {self.gl_project.path_with_namespace}')
        if not self.force:
            print('  skipping without --force')
            return
        gl_fork = self.gl_project.forks.create()
        gl_fork = self.gl_instance.projects.get(gl_fork.id)
        while gl_fork.import_status != 'finished':
            print(f'Waiting for import to finish: {gl_fork.import_status}')
            time.sleep(5)
            gl_fork.refresh()

    def create_fork_remote(self, remote_name):
        """Set up fork remote."""
        if not os.path.exists(self.full_project_path):
            return
        git_repo = git.Repo(self.full_project_path)
        if git.Remote(git_repo, remote_name).exists():
            return
        print(f'Creating fork remote {remote_name} for {self.gl_project.path_with_namespace}')
        if not self.force:
            print('  skipping without --force')
            return
        gl_fork = self.gl_project.forks.list(owned=True)[0]
        git_repo.create_remote(remote_name, gl_fork.ssh_url_to_repo).fetch()

    def create_venv(self):
        """Set up Python venv."""
        if not os.path.exists(self.full_project_path):
            return
        envrc_path = pathlib.Path(f'{self.full_project_path}/.envrc')
        setup_cfg_path = pathlib.Path(f'{self.full_project_path}/setup.cfg')
        if envrc_path.exists():
            return
        if not (os.path.exists(f'{self.full_project_path}/requirements.txt') or
                setup_cfg_path.exists()):
            return
        print('Creating Python venv')
        if not self.force:
            print('  skipping without --force')
            return
        if setup_cfg_path.exists():
            setup_cfg = configparser.ConfigParser()
            setup_cfg.read_string(setup_cfg_path.read_text(encoding='utf8'))
            extras = setup_cfg.get('testenv', 'extras', fallback='').lstrip().replace('\n', ',')
            packages = ['-e', f'.[{extras}]'] if extras else ['-e', '.']
        else:
            packages = ['-r', 'requirements.txt']
        test_packages = ['flake8', 'autopep8', 'pydocstyle',
                         'isort[colors]', 'pylint', 'pytest', 'coverage']
        envrc_path.write_text('layout python python3.11\nsource_up', encoding='utf8')
        subprocess.run(['direnv', 'allow', '.'],
                       cwd=self.full_project_path, check=True)
        subprocess.run(['direnv', 'exec', self.full_project_path, 'pip', 'install', 'wheel'],
                       cwd=self.full_project_path, check=True)
        subprocess.run(['direnv', 'exec', self.full_project_path, 'pip', 'install'] +
                       test_packages + packages,
                       cwd=self.full_project_path, check=True)


def main(args):
    """Run main program."""
    parser = argparse.ArgumentParser(
        description='Checkout active CKI projects to a local directory.')
    parser.add_argument('--directory', default=os.getcwd(),
                        help='Parent directory for repositories, defaults to the current directory')
    parser.add_argument('--force', action='store_true',
                        help='Actually clone the repositories')
    parser.add_argument('--fork', metavar='REMOTE',
                        help='Create and clone fork as the given remote')
    parser.add_argument('--venv', action='store_true',
                        help='Setup and populate venv via direnv and pip')
    parsed_args = parser.parse_args(args)
    for group in groups:
        print(f'Checking {group.instance_url}/{group.path}')
        with get_instance(group.instance_url) as gl_instance:
            for gl_project in gl_instance.groups.get(group.path, lazy=True).projects.list(
                    iterator=True, include_subgroups=True, archived=False):
                if not gl_project.path_with_namespace.startswith(group.path):
                    continue
                if not any(gl_project.path_with_namespace.startswith(f'{group.path}/{b}')
                           for b in group.allowlist) and group.allowlist:
                    continue
                if any(gl_project.path_with_namespace.startswith(f'{group.path}/{b}')
                        for b in group.blocklist):
                    continue
                sub_path = gl_project.path_with_namespace[len(group.path) + 1:]
                project_name = group.aliases.get(sub_path, sub_path.replace('/', '-'))
                repo_manager = RepoManager(gl_project, project_name,
                                           parsed_args.directory, parsed_args.force)
                repo_manager.clone()
                if parsed_args.fork:
                    repo_manager.create_fork()
                    repo_manager.create_fork_remote(parsed_args.fork)
                if parsed_args.venv:
                    repo_manager.create_venv()


if __name__ == '__main__':
    misc.sentry_init(sentry_sdk)
    main(sys.argv[1:])
